package com.hzb.LNReader.reader;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;

import com.hzb.LNReader.ReaderRecord;
import com.hzb.LNReader.util.ReaderPref;

public class FileReader {
	private final int bufferLimit;
	private final int seekBackSize;
	private final int bufferReadLine;
	private final int startReadLine;
	
	private byte[] readBuffer;
	private int begin;
	private int end;
	private int currSeek;

	public String[] contentLine;
	public int[] lineOffset;
	
	private int bufferLineSeqFrom;
	private int bufferBeginIndex;
	public int bufferSize;

	private ArrayList<String> tmpLineBuf;
	private ArrayList<Integer> tmpLineOffset;

	private ReaderRecord currFile;
	private FileInputStream reader;
	private int fileSize;
	private final int BUFFER_SIZE = 32768;
	private final byte CL = '\n';
	private final byte CR = '\r';
	public String encoding;

	private String readLine() throws IOException {
		int last = begin;
		int head = begin;
		for (int i = begin;; i++) {
			if (i == BUFFER_SIZE && begin == 0) throw new IOException("Line too long");
			if (i == end) {
				if (begin != 0) {
					System.arraycopy(readBuffer, begin, readBuffer, 0, end -= begin);
					begin = 0;
					head = 0;
					i = end;
				}
				int bytesRead = reader.read(readBuffer, end, BUFFER_SIZE - end);
				if (bytesRead == -1) {
					if (begin == i) {
						return null;
					} else {
						begin = i;
						last = i;
						break;
					}
				} else if (bytesRead == 0)
					continue;
				end += bytesRead;
			}
			if (readBuffer[i] == CL) {
				if (i == 0 || readBuffer[i - 1] != CR) {
					last = i;
					currSeek++;
				} else {
					last = i - 1;
					currSeek += 2;
				}
				begin = i + 1;
				break;
			}
		}
		int len = last - head;
		currSeek += len;
		return new String(readBuffer, head, len, encoding);
	}

	public void fastLoadFile(ReaderRecord fileRecord) throws IOException {
		currFile = fileRecord;
		encoding = fileRecord.encoding;
		reader = new FileInputStream(fileRecord.file);
		fileSize = (int) fileRecord.file.length();
		try {
			bufferBeginIndex = 0;
			currFile.maxLineSeq = fileRecord.maxLineSeq;
			currSeek = fileRecord.fileOffset;
			reader.skip(currSeek);
			bufferLineSeqFrom = fileRecord.startLineSeq;

			int i = 0;
			for (; i < startReadLine; i++) {
				lineOffset[i] = currSeek;
				contentLine[i] = readLine();
				if (contentLine[i] == null) {
					currFile.maxLineSeq = bufferLineSeqFrom + i;
					break;
				}
			}
			lineOffset[i] = currSeek;
			bufferSize = i;
		} finally {
			FileInputStream tmp = reader;
			reader = null;
			tmp.close();
		}
	}

	public synchronized int ensureFileBufferByOffset(int offset)
			throws IOException {
		if (offset > fileSize) offset = fileSize;
		while (bufferLineSeqFrom + bufferSize < currFile.maxLineSeq 
				&& lineOffset[(bufferBeginIndex + bufferSize) % lineOffset.length] < offset) {
			readFileForward();
		}

		while (lineOffset[bufferBeginIndex] > fileSize) {
			readFileBackword();
		}
		
		int bufferEndIndex = bufferBeginIndex + bufferSize;
		if (bufferEndIndex > lineOffset.length) bufferEndIndex -= lineOffset.length;
		int ret;
		if (bufferEndIndex > bufferBeginIndex) {
			ret = binarySearch(lineOffset, bufferBeginIndex, bufferEndIndex, offset);
		} else {
			if (lineOffset[0] > offset) {
				ret = binarySearch(lineOffset, bufferBeginIndex, lineOffset.length, offset);
			} else {
				ret = binarySearch(lineOffset, 0, bufferEndIndex, offset);
			}
		}
		if (ret < 0) ret = -ret - 1;
		ret -= bufferBeginIndex;
		if (ret < 0) ret += lineOffset.length;
		return ret + bufferLineSeqFrom;
	}

	private static int binarySearch(int[] a, int fromIndex, int toIndex,
			int key) {
		int low = fromIndex;
		int high = toIndex - 1;

		while (low <= high) {
			int mid = (low + high) >>> 1;
			int midVal = a[mid];

			if (midVal < key)
				low = mid + 1;
			else if (midVal > key)
				high = mid - 1;
			else
				return mid; // key found
		}
		return -(low + 1); // key not found.
	}
	/**
	 * 返回指定seq范围对应的缓存区段头尾下标
	 * @param fromLineSeq
	 * @param size
	 * @return ret[0]为区段头下标（包含）,ret[1]为区段尾下标（不包含）
	 * @throws IOException
	 */
	public synchronized int[] ensureFileBuffer(int fromLineSeq, int size)
			throws IOException {
		while (bufferLineSeqFrom + bufferSize < currFile.maxLineSeq
				&& fromLineSeq + size > bufferLineSeqFrom + bufferSize) {
			readFileForward();
		}

		while (lineOffset[bufferBeginIndex] > 0
				&& fromLineSeq < bufferLineSeqFrom) {
			readFileBackword();
		}
		int[] ret = new int[2];
		int endSeq;
		if (fromLineSeq + size > currFile.maxLineSeq) {
			endSeq = currFile.maxLineSeq;
		} else {
			endSeq = fromLineSeq + size;
		}
		ret[0] = bufferBeginIndex + fromLineSeq - bufferLineSeqFrom;
		ret[1] = bufferBeginIndex + endSeq - bufferLineSeqFrom;
		if (ret[0] >= contentLine.length)
			ret[0] -= contentLine.length;
		if (ret[1] >= contentLine.length)
			ret[1] -= contentLine.length;
		return ret;
	}

	private void openFileAndSeek(int seek) throws IOException {
		reader = new FileInputStream(currFile.file);
		currSeek = seek;
		reader.skip(currSeek);
		begin = end = 0;
	}

	private void readFileForward() throws IOException {
		int i = bufferBeginIndex + bufferSize;
		int lastIndex = i - 1;
		if (lastIndex >= contentLine.length)
			lastIndex -= contentLine.length;
		openFileAndSeek(lineOffset[lastIndex] + contentLine[lastIndex].length());
		try {
			readLine();

			int c = 0;
			for (; c < bufferReadLine; i++, c++) {
				if (i >= contentLine.length) {
					i -= contentLine.length;
				}
				lineOffset[i] = currSeek;
				contentLine[i] = readLine();
				if (contentLine[i] == null) {
					currFile.maxLineSeq = bufferLineSeqFrom + bufferSize + c;
					break;
				}
			}
			lineOffset[i] = currSeek;
			bufferSize += c;
			if (bufferSize >= contentLine.length) {
				int maxSize = contentLine.length - 1;
				bufferLineSeqFrom += bufferSize - maxSize;
				bufferBeginIndex = i + 1;
				if (bufferBeginIndex >= contentLine.length)
					bufferBeginIndex -= contentLine.length;
				bufferSize = maxSize;
			}
		} finally {
			FileInputStream tmp = reader;
			reader = null;
			tmp.close();
		}
	}

	private void readFileBackword() throws IOException {
		int bufferHeadSeek = lineOffset[bufferBeginIndex];
		currSeek = bufferHeadSeek - seekBackSize;
		if (currSeek < 0)
			currSeek = 0;
		openFileAndSeek(currSeek);
		try {
			tmpLineBuf.clear();
			tmpLineOffset.clear();
			if (currSeek != 0)
				readLine();
			for (;;) {
				int seek = currSeek;
				if (seek >= bufferHeadSeek)
					break;
				String line = readLine();
				tmpLineBuf.add(line);
				tmpLineOffset.add(seek);
			}
			int c = 0;
			int di = bufferBeginIndex - 1;
			int cl = Math.max(0, tmpLineBuf.size()
					- bufferReadLine);
			for (int si = tmpLineBuf.size() - 1; si >= cl; si--, di--, c++) {
				if (di < 0)
					di += contentLine.length;
				contentLine[di] = tmpLineBuf.get(si);
				lineOffset[di] = tmpLineOffset.get(si);
			}

			bufferSize += c;
			bufferLineSeqFrom -= c;
			bufferBeginIndex = di + 1;
			if (bufferSize >= contentLine.length) {
				bufferSize = contentLine.length - 1;
			}
		} finally {
			FileInputStream tmp = reader;
			reader = null;
			tmp.close();
		}
	}

	public FileReader() {
		bufferLimit = ReaderPref.getBufferSize();
		bufferReadLine = ReaderPref.getBufferReadLine();
		seekBackSize = ReaderPref.getSeekBackSize();
		startReadLine = ReaderPref.getStartReadLine();
		
		contentLine = new String[bufferLimit];
		lineOffset = new int[bufferLimit];

		tmpLineBuf = new ArrayList<String>(bufferReadLine);
		tmpLineOffset = new ArrayList<Integer>(bufferReadLine);
		readBuffer = new byte[32768];
	}
}
